package com.ctshk.rpc.user.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ctshk.common.constant.RedisConstants;
import com.ctshk.common.dto.Result;
import com.ctshk.common.enums.*;
import com.ctshk.common.exception.BusinessException;
import com.ctshk.common.mail.core.MailTemplate;
import com.ctshk.common.sms.core.SmsUtil;
import com.ctshk.common.utils.EntityUtil;
import com.ctshk.common.utils.MD5Util;
import com.ctshk.common.utils.SnowflakeIdWorker;
import com.ctshk.rpc.starvip.dto.start_vip.user.Contact;
import com.ctshk.rpc.starvip.dto.start_vip.user.LoyMember;
import com.ctshk.rpc.starvip.dto.start_vip.user.LoyMemberDominantTier;
import com.ctshk.rpc.starvip.req.member.MemberRegisterReq;
import com.ctshk.rpc.starvip.req.user.StarVipUserCreateOpUpdateReq;
import com.ctshk.rpc.starvip.req.user.UserInfoQueryReq;
import com.ctshk.rpc.starvip.service.IStarVipUserService;
import com.ctshk.rpc.user.config.SmsConfig;
import com.ctshk.rpc.user.dto.NumDTO;
import com.ctshk.rpc.user.dto.AppUserDTO;
import com.ctshk.rpc.user.entity.User;
import com.ctshk.rpc.user.mapper.UserMapper;
import com.ctshk.rpc.user.req.*;
import com.ctshk.rpc.user.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import javax.validation.Valid;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author 谢诗宏
 * @description 用户service实现类
 * @date 2020/12/9
 */
@Slf4j
@DubboService
public class UserServiceImpl implements IUserService {

    @DubboReference
    private IUserValidateService userValidateService;

    @DubboReference
    private IStarVipUserService starVipUserService;

    @DubboReference
    private IUserMessageService userMessageService;

    @DubboReference
    private IUserCollectionService userCollectionService;

    @DubboReference
    private IUserFootPointService userFootPointService;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private SmsConfig smsConfig;

    @Autowired
    private MailTemplate mailTemplate;

    //判断是邮箱还是手机号的正则表达式
    public static String emailReg = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
    public static String phoneReg = "^[0-9]{8,11}$";


    @Override
    public Result<AppUserDTO> register(RegisterReq registerReq) throws Exception{
        userValidateService.check(registerReq.getFullContactNumber(),registerReq.getCode(),SmsType.REGISTER.getType());


        List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().eq(User::getContactNumber, registerReq.getContactNumber()));
        if(CollectionUtils.isNotEmpty(userList)) {
            return Result.failed(SystemError.USER_1029);
        }
        String password = RandomUtil.randomNumbers(6);
        long userId = addUser(registerReq, password);
        userMessageService.sysNotify(userId,"注册成功","恭喜您成功注册港中旅");
        try {
            if(StringUtils.isNotBlank(registerReq.getEnName()) && StringUtils.isNotBlank(registerReq.getEnSurname())) {
                // 查询用户,如果是星赏会会员则把星赏会会员信息填充进t_user表
                UserInfoQueryReq userInfoQueryReq = new UserInfoQueryReq();
                userInfoQueryReq.setCellularPhone(registerReq.getContactMobileAreaCode() + "-" + registerReq.getContactNumber());
                Result<Contact> contactResult = starVipUserService.userInfoQuery(userInfoQueryReq);
                if(contactResult.isSuccess()) {
                    Contact data = contactResult.getData();
                    if(data != null) {
                        StarVipUserCreateOpUpdateReq param = new StarVipUserCreateOpUpdateReq();
                        param.setLastName(registerReq.getEnSurname());
                        param.setFirstName(registerReq.getEnName());
                        param.setId(data.getId());
                        Result<StarVipUserCreateOpUpdateReq> result = starVipUserService.createOrUpdate(param);
                        if(result.isSuccess()) {
                            registerSuccessVip(userId,data);
                        }
                    } else {
                        //构建参数实体
                        MemberRegisterReq param = buildRegisterParam(registerReq,password);
                        Result<MemberRegisterReq> result = starVipUserService.register(param);
                        if(result.isSuccess()) {
                            //注册成功修改本地会员为星赏会会员
                            registerSuccessVip(userId,result);
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            log.error("注册星赏会会员失败");
        }

        Map<String,String> templateParam = new HashMap<>();
        templateParam.put("password",password);
        templateParam.put("name", smsConfig.getName());
        if(SmsUtil.isDomestic(registerReq.getContactMobileAreaCode())) {
            SmsUtil.sendSms(registerReq.getContactNumber(),smsConfig.getSignName(),smsConfig.getRegiterSuccessTemplateCode(),templateParam);
        } else {
            SmsUtil.sendSms(registerReq.getContactNumber(),smsConfig.getSignName(),smsConfig.getInternationalRegiterSuccessTemplateCode(),templateParam);
        }

        AppUserDTO u = loginByPhone(registerReq.getContactMobileAreaCode(),registerReq.getContactNumber());

        return Result.success(u);
    }

    private MemberRegisterReq buildRegisterParam(RegisterReq registerReq,String password) throws Exception{
        MemberRegisterReq param = new MemberRegisterReq();
        param.setCellularPhone(registerReq.getContactMobileAreaCode() + "-" + registerReq.getContactNumber());
        param.setCtsLastName(registerReq.getEnName());
        param.setCtsFstName(registerReq.getEnSurname());
        //未知性别
        param.setMf("99");
        String ctsLogin = String.valueOf(System.currentTimeMillis());
        param.setCtsLogin(ctsLogin);
        param.setEnrollmentChannel("11");
        param.setCtsCardSegment("11");
        param.setCtsPassword(MD5Util.stringMd5(password));
        param.setActiveFlag("Y");
        param.setCardStatusCode("Active");
        param.setCardType("A00000");
        param.setCtsCompanyNumber("2199");

        return param;
    }

    @Override
    public Result smsCaptcha(String contactMobileAreaCode,String contactNumber,Integer type) throws Exception{
        userValidateService.sendSmsCaptcha(contactMobileAreaCode,contactNumber,type);
        return Result.success();
    }

    @Override
    public Result resetPass(ForgetPwdReq forgetPwdReq) throws Exception{
        userValidateService.check(forgetPwdReq.getFullContactNumber(),forgetPwdReq.getCode(), SmsType.RESET_PWD.getType());

        AppUserDTO u = loadUserByParam(forgetPwdReq.getContactNumber(), 3);

        try {
            String passsword = BCrypt.hashpw(forgetPwdReq.getPassword(), BCrypt.gensalt());
            checkRecentPwd(u.getId(),forgetPwdReq.getPassword(),u.getPassword());
            resetPassword(u.getId(), passsword);
        }  catch (BusinessException e) {
            log.error("修改密码錯誤: ",e);
            return Result.failed(e);
        } catch (Exception e) {
            return Result.failed(SystemError.USER_1014);
        }


        return Result.success();
    }

    @Override
    public AppUserDTO login(AppUserDTO u) {
        String userName = u.getUserAccount();
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        if(userName.matches(emailReg)) {
            wrapper.lambda().eq(User::getEmail, u.getUserAccount());
        } else if (userName.matches(phoneReg)) {
            wrapper.lambda().eq(User::getContactNumber, u.getUserAccount());
        } else {
            wrapper.lambda().eq(User::getUserAccount, u.getUserAccount());
        }

        wrapper.lambda().eq(User::getIsDeleted, 0);
        User user = userMapper.selectOne(wrapper);
        if (user == null) {
            throw new BusinessException(SystemError.USER_1002);
        }

        String lockoutKey = RedisConstants.APP_ACCOUNT_LOCKOUT + user.getId();
        Object lockout = redisTemplate.opsForValue().get(lockoutKey);

        if (null != lockout) {
            throw new BusinessException(SystemError.USER_1004);
        }

        if (!BCrypt.checkpw(u.getPassword(), user.getPassword())) {
            // 增加当天登录错误次数,每天5次
            String key = RedisConstants.APP_ACCOUNT_LOCKOUT_TIMES + user.getId();
            Object lockoutTimes = redisTemplate.opsForValue().get(key);
            int count = 1;
            if (null != lockoutTimes) {
                count = Integer.parseInt(lockoutTimes.toString()) + 1;
            }
            redisTemplate.opsForValue().set(key, count,24, TimeUnit.HOURS);
            if (count >= 5) {
                redisTemplate.opsForValue().set(lockoutKey, 1, 1, TimeUnit.DAYS);
                throw new BusinessException(SystemError.USER_1004);
            }

            throw new BusinessException(SystemError.USER_1003, 5-count);
        }

        /*LocalDateTime lastChangePwd = sysUser.getLastChangePwd();
        LocalDateTime end = LocalDateTime.now();
        Duration duration = Duration.between(lastChangePwd,end);
        log.info("上次修改密码时间[{}],已经[{}]天", lastChangePwd, duration.toDays());
        if (duration.toDays() >= 90) {
            throw new BusinessException(SystemError.USER_1010);
        }*/
        AppUserDTO userDto = new AppUserDTO();

        BeanUtils.copyProperties(user, userDto);
        // userDto.setFullName(StrUtil.concat(true, sysUser.getCnSurname(), sysUser.getCnName()));
        if(userDto.getMemberCode() == null) {
            userDto.setMemberCode("CTS Member");
        }
        log.info("userDto[{}]", userDto);

        return userDto;
    }

    @Override
    public AppUserDTO noPwdLogin(String userName) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        if(userName.matches(emailReg)) {
            wrapper.lambda().eq(User::getEmail, userName);
        } else if (userName.matches(phoneReg)) {
            wrapper.lambda().eq(User::getContactNumber, userName);
        } else {
            wrapper.lambda().eq(User::getUserAccount, userName);
        }

        wrapper.lambda().eq(User::getIsDeleted, 0);
        User user = userMapper.selectOne(wrapper);
        if (user == null) {
            throw new BusinessException(SystemError.USER_1002);
        }

        String lockoutKey = RedisConstants.APP_ACCOUNT_LOCKOUT + user.getId();
        Object lockout = redisTemplate.opsForValue().get(lockoutKey);

        if (null != lockout) {
            throw new BusinessException(SystemError.USER_1004);
        }

        AppUserDTO userDto = new AppUserDTO();

        BeanUtils.copyProperties(user, userDto);
        if(userDto.getMemberCode() == null) {
            userDto.setMemberCode("CTS Member");
        }
        log.info("userDto[{}]", userDto);

        return userDto;
    }

    @Override
    public AppUserDTO loginByPhone(String contactMobileAreaCode, String contactNumber) {
        User user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getContactMobileAreaCode,contactMobileAreaCode)
        .eq(User::getContactNumber, contactNumber)
        .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
        if (user == null) {
            throw new BusinessException(SystemError.USER_1002);
        }
        String lockoutKey = RedisConstants.APP_ACCOUNT_LOCKOUT + user.getId();
        Object lockout = redisTemplate.opsForValue().get(lockoutKey);

        if (null != lockout) {
            throw new BusinessException(SystemError.USER_1004);
        }

        AppUserDTO userDto = new AppUserDTO();

        BeanUtils.copyProperties(user, userDto);
        if(userDto.getMemberCode() == null) {
            userDto.setMemberCode("CTS Member");
        }
        log.info("userDto[{}]", userDto);

        return userDto;
    }

    @Override
    public boolean exists(AppUserDTO userDTO) {
        Integer count = userMapper.selectCount(Wrappers.<User>lambdaQuery()
                .eq(userDTO.getContactMobileAreaCode() != null,User::getContactMobileAreaCode, userDTO.getContactMobileAreaCode())
                .eq(userDTO.getContactNumber() != null,User::getContactNumber, userDTO.getContactNumber())
                .eq(userDTO.getEmail() != null,User::getContactNumber, userDTO.getEmail())
        .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
        return count > 0;
    }

    @Override
    public AppUserDTO getUser(AppUserDTO u) {
        User sysUser = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUserAccount, u.getUserAccount()));
        if (sysUser == null) {
            throw new BusinessException(SystemError.USER_1002);
        }

        AppUserDTO userDto = new AppUserDTO();

        BeanUtils.copyProperties(sysUser, userDto);

        return userDto;
    }

    @Override
    public AppUserDTO loadUserByParam(Object param, Integer queryType) {
        User user;
        if (queryType == 1) {
            user = userMapper.selectById((Serializable) param);
        } else if (queryType == 2) {
            user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUserAccount, param));
        } else if (queryType == 3) {
            user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getContactNumber, param));
        } else if (queryType == 4) {
            user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getEmail, param));
        } else {
            user = null;
        }
        if (user == null || (user != null && user.getIsDeleted() == 1)) {
            throw new BusinessException(SystemError.USER_1002);
        }
        AppUserDTO userDto = new AppUserDTO();
        BeanUtils.copyProperties(user, userDto);
        return userDto;
    }

    @Override
    public AppUserDTO loadUserInfo(Object param, Integer queryType) {
        User user;
        if (queryType == 1) {
            user = userMapper.selectById((Serializable) param);
        } else if (queryType == 2) {
            user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getUserAccount, param));
        } else if (queryType == 3) {
            user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getContactNumber, param));
        } else if (queryType == 4) {
            user = userMapper.selectOne(Wrappers.<User>lambdaQuery().eq(User::getEmail, param));
        } else {
            user = null;
        }
        if (user == null) {
            return null;
        }
        AppUserDTO userDto = new AppUserDTO();
        if (user != null) {
            BeanUtils.copyProperties(user, userDto);
        }
        return userDto;
    }

    @Override
    public AppUserDTO queryCacheUser(long userId){
        AppUserDTO userDto = null;
        try {
            userDto = (AppUserDTO) redisTemplate.opsForValue().get(RedisConstants.APP_ACCOUNT_QUERY_KEY + userId);
            if(userDto == null){
                insertCacheUser();
                userDto = (AppUserDTO) redisTemplate.opsForValue().get(RedisConstants.APP_ACCOUNT_QUERY_KEY + userId);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return userDto;
    }

    @Override
    public void insertCacheUser() {
        List<User> users = userMapper.selectList(null);
        List<AppUserDTO> userDTOS = EntityUtil.copyList(users, AppUserDTO.class);
        userDTOS.forEach(user -> {
            try {
                if(user.getMemberCode() == null) {
                    user.setMemberCode("CTS Member");
                }
                redisTemplate.opsForValue().set(RedisConstants.APP_ACCOUNT_QUERY_KEY + user.getId(), user);
            } catch (Exception e) {
                log.error("用户[{}]插入缓存失败,错误信息[{}]", user.getId(), e.getMessage());
            }
        });
    }

    @Override
    public void resetPassword(long userId, String password) {
        User user = new User();
        LocalDateTime now = LocalDateTime.now();

        user.setId(userId);
        user.setPassword(password);
        user.setModifiedId(userId);
        user.setGmtModified(now);
        userMapper.updateById(user);
        redisTemplate.opsForList().leftPush(RedisConstants.RECENT_PASSWORD_USER_ID + userId, password);
    }

    @Override
    public long addUser(RegisterReq registerReq,String password) {
        User user = EntityUtil.copy(registerReq, User.class);
        long id = SnowflakeIdWorker.nextId();
        user.setId(id);
        if(StringUtils.isNotBlank(user.getEnSurname()) && StringUtils.isNotBlank(user.getEnName())) {
            user.setEnFullname(user.getEnSurname() + user.getEnName());
        }
        String encryptPassword = BCrypt.hashpw(password, BCrypt.gensalt());
        user.setPassword(encryptPassword);
        user.setIsInternalMember(IsInternalMemberCode.NO.getCode());
        user.setIsDeleted(IsDeletedCode.NO.getCode());
        user.setMemberPoints(0);
        LocalDateTime now = LocalDateTime.now();
        user.setGmtCreate(now);
        user.setGmtModified(now);
        userMapper.insert(user);
        redisTemplate.opsForList().leftPush(RedisConstants.RECENT_PASSWORD_USER_ID + id, encryptPassword);
        return user.getId();
    }

    @Override
    public Result saveUserInfo(SaveUserInfoReq req) {
        User user = EntityUtil.copy(req, User.class);
        Long userId = req.getUserId();
        user.setEnFullname(req.getEnSurname() + req.getEnName());
        user.setModifiedId(userId);
        user.setGmtModified(LocalDateTime.now());
        userMapper.update(user,new UpdateWrapper<User>().lambda()
        .eq(User::getId,userId)
        .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
        updateCache(userId);

        return Result.success();
    }

    /**
     * 更新缓存里的用户
     * @param userId
     */
    private AppUserDTO updateCache(Long userId) {
        AppUserDTO userDTO = null;
        try {
            //更新缓存
            User newestUser = userMapper.selectById(userId);
            userDTO = EntityUtil.copy(newestUser, AppUserDTO.class);
            redisTemplate.opsForValue().set(RedisConstants.APP_ACCOUNT_QUERY_KEY + userId, userDTO);

        } catch (Exception e) {
            log.error("用户[{}]插入缓存失败,错误信息[{}]", userId, e.getMessage());
        }
        return userDTO;
    }

    @Override
    public Result updatePwd(UpdatePwdReq req) {
        try {
            userValidateService.check(null, req.getCode(), SmsType.UPDATE_PWD.getType(),req.getUserId());
            Long userId = req.getUserId();
            AppUserDTO userDTO = loadUserInfo(userId,1);
            User user = EntityUtil.copy(req, User.class);
            boolean checkpw = BCrypt.checkpw(req.getOldPassword(), userDTO.getPassword());
            if(checkpw) {
                String password = BCrypt.hashpw(req.getNewPassword(), BCrypt.gensalt());
                // 校验密码是否是最近使用过
                checkRecentPwd(userDTO.getId(), req.getNewPassword(),userDTO.getPassword());
                user.setPassword(password);

                user.setModifiedId(userId);
                user.setGmtModified(LocalDateTime.now());
                userMapper.update(user,new UpdateWrapper<User>().lambda()
                        .eq(User::getId, userId)
                        .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));

                redisTemplate.opsForList().leftPush(RedisConstants.RECENT_PASSWORD_USER_ID + userId, password);
                return Result.success();
            } else {
                return Result.failed(new BusinessException(SystemError.SYS_433,"原密碼錯誤"));
            }

        } catch (BusinessException e) {
            log.error("修改密码錯誤: ",e);
            return Result.failed(new BusinessException(SystemError.SYS_433,e.getMessage()));
        } catch (Exception e) {
            log.error("修改密码錯誤: ",e);
            return Result.failed(new BusinessException(SystemError.SYS_433,"修改密码錯誤"));
        }
    }

    @Override
    public Result updatePhone(UpdatePhoneReq req) throws Exception{
        userValidateService.check(null, req.getOldCode(), SmsType.UPDATE_PHONE.getType(),req.getUserId());
        userValidateService.check(req.getContactMobileAreaCode() + req.getContactNumber(), req.getNewCode(), SmsType.UPDATE_PHONE.getType());
        try {

            // 查詢新號碼有沒有被使用,被使用則提示
            User one = userMapper.selectOne(new QueryWrapper<User>().lambda()
                    .eq(User::getContactMobileAreaCode, req.getContactMobileAreaCode())
                    .eq(User::getContactNumber, req.getContactNumber()));
            if(one != null) {
                return Result.failed(new BusinessException(SystemError.SYS_433,"該手機號碼已被使用"));
            }
            AppUserDTO userDTO = queryCacheUser(req.getUserId());
            if(userDTO.getIsInternalMember() == 1) {
                UserInfoQueryReq userInfoQueryReq = new UserInfoQueryReq();
                userInfoQueryReq.setCtsLogin(userDTO.getUserAccount());
                Result<Contact> contactResult = starVipUserService.userInfoQuery(userInfoQueryReq);
                if(contactResult.isSuccess()) {
                    Contact data = contactResult.getData();
                    StarVipUserCreateOpUpdateReq param = new StarVipUserCreateOpUpdateReq();
                    param.setCellularPhone(req.getContactMobileAreaCode() + "-" + req.getContactNumber());
                    param.setId(data.getId());
                    Result<StarVipUserCreateOpUpdateReq> result = starVipUserService.createOrUpdate(param);
                    if(result.isSuccess()) {
                        //星赏会会员手机修改成功才修改本地手机
                        User user = EntityUtil.copy(req, User.class);
                        Long userId = req.getUserId();
                        user.setModifiedId(userId);
                        user.setGmtModified(LocalDateTime.now());
                        userMapper.update(user,new UpdateWrapper<User>().lambda()
                                .eq(User::getId, userId)
                                .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
                        updateCache(userId);
                        return Result.success();
                    }
                }

            } else {
                User user = EntityUtil.copy(req, User.class);
                Long userId = req.getUserId();
                user.setModifiedId(userId);
                user.setGmtModified(LocalDateTime.now());
                userMapper.update(user,new UpdateWrapper<User>().lambda()
                        .eq(User::getId, userId)
                        .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
                updateCache(userId);
                return Result.success();
            }



        } catch (Exception e) {
            log.error("修改手機錯誤: ",e);
            return Result.failed(new BusinessException(SystemError.SYS_433,"修改手机失败"));
        }
        return Result.failed(new BusinessException(SystemError.SYS_433,"修改手机失败"));
    }

    @Override
    public Result mail(String email, Long userId) {
        AppUserDTO userDTO  = queryCacheUser(userId);;
        String suffix = "";

        if(StringUtils.isNotBlank(email)) {
            // 校驗郵箱
            if(!email.matches(emailReg)) {
                return Result.failed(new BusinessException(SystemError.SYS_433,"请输入规范邮箱格式"));
            }
            List<User> userList= userMapper.selectList(Wrappers.<User>lambdaQuery().eq(User::getEmail,email));
            if(CollectionUtils.isNotEmpty(userList)) {
                return Result.failed(new BusinessException(SystemError.SYS_433,"該郵箱已被使用"));
            }
            suffix = "_new";
        } else {
            if(userDTO == null) {
                return Result.failed(new BusinessException(SystemError.USER_1015));
            }
            String userDTOEmail = userDTO.getEmail();
            if(userDTOEmail == null) {
                return Result.failed(new BusinessException(SystemError.SYS_433,"请先设置邮箱"));
            }
        }
        String key = RedisConstants.APP_EMAIL_CODE_KEY + userDTO.getId() + suffix;
        Object o = redisTemplate.opsForValue().get(key);

        if(o != null) {
            // 邮件间隔两分钟可重发
            Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
            if(expire > 15 * 60 - 120) {
                throw new BusinessException(SystemError.USER_1024);
            }
        }
        String title = "修改邮箱";
        String content = "您正在修改邮箱，验证码：";
        String code = RandomUtil.randomNumbers(4);
        content = content + code;

        String sendMail = StringUtils.isNotBlank(suffix)?email:userDTO.getEmail();
        redisTemplate.opsForValue().set(key, code,15, TimeUnit.MINUTES);
        mailTemplate.sendSimpleMail(sendMail, title, content);

        return Result.success();
    }

    @Override
    public Result updateEmail(UpdateEmailReq req) {
        Long userId = req.getUserId();
        AppUserDTO userDTO = loadUserByParam(userId, 1);
        String oldEmailKey = RedisConstants.APP_EMAIL_CODE_KEY + userDTO.getId();
        String newEmailKey = RedisConstants.APP_EMAIL_CODE_KEY + userDTO.getId() + "_new";
        Object oldTrueCode = redisTemplate.opsForValue().get(oldEmailKey);
        if (oldTrueCode == null) {
            return Result.failed(SystemError.USER_1016);
        }
        Object newTrueCode = redisTemplate.opsForValue().get(newEmailKey);
        if (newTrueCode == null) {
            return Result.failed(SystemError.USER_1016);
        }
        if (!StringUtils.equals(req.getOldCode(), oldTrueCode.toString())) {
            return Result.failed(SystemError.USER_1017);
        }
        if (!StringUtils.equals(req.getNewCode(), newTrueCode.toString())) {
            return Result.failed(SystemError.USER_1017);
        }

        if(userDTO.getIsInternalMember() == 1) {
            UserInfoQueryReq userInfoQueryReq = new UserInfoQueryReq();
            userInfoQueryReq.setCtsLogin(userDTO.getUserAccount());
            Result<Contact> contactResult = starVipUserService.userInfoQuery(userInfoQueryReq);
            if(contactResult.isSuccess()) {
                Contact data = contactResult.getData();
                StarVipUserCreateOpUpdateReq param = new StarVipUserCreateOpUpdateReq();
                param.setEmailAddress(req.getEmail());
                param.setId(data.getId());
                Result<StarVipUserCreateOpUpdateReq> result = starVipUserService.createOrUpdate(param);
                if(result.isSuccess()) {
                    //星赏会会员邮箱修改成功才修改本地邮箱
                    User user = EntityUtil.copy(req, User.class);
                    user.setModifiedId(userId);
                    user.setGmtModified(LocalDateTime.now());
                    userMapper.update(user,new UpdateWrapper<User>().lambda()
                            .eq(User::getId, userId)
                            .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
                    updateCache(userId);
                    redisTemplate.delete(oldEmailKey);
                    redisTemplate.delete(newEmailKey);
                    return Result.success();
                }
            }

        } else {
            User user = EntityUtil.copy(req, User.class);
            user.setModifiedId(userId);
            user.setGmtModified(LocalDateTime.now());
            userMapper.update(user,new UpdateWrapper<User>().lambda()
                    .eq(User::getId, userId)
                    .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
            updateCache(userId);
            redisTemplate.delete(oldEmailKey);
            redisTemplate.delete(newEmailKey);
            return Result.success();
        }
        return Result.failed(new BusinessException(SystemError.SYS_433,"修改邮箱失败"));
    }

    @Override
    public Result<AppUserDTO> registerStartVip(RegisterStartVipReq req) {
        Long userId = req.getUserId();
        AppUserDTO userDTO = queryCacheUser(userId);
        if(userDTO.getIsInternalMember() == 1) {
            return Result.failed(new BusinessException(SystemError.SYS_433,"当前用户已经是星赏会会员"));
        }

        // 查询用户,如果是星赏会会员则把星赏会会员信息填充进t_user表
        UserInfoQueryReq userInfoQueryReq = new UserInfoQueryReq();
        userInfoQueryReq.setCellularPhone(userDTO.getContactMobileAreaCode() + "-" + userDTO.getContactNumber());
        Result<Contact> contactResult = starVipUserService.userInfoQuery(userInfoQueryReq);
        if(contactResult.isSuccess()) {
            Contact data = contactResult.getData();
            if(data != null) {
                StarVipUserCreateOpUpdateReq param = new StarVipUserCreateOpUpdateReq();
                param.setLastName(req.getEnSurname());
                param.setFirstName(req.getEnName());
                param.setId(data.getId());
                Result<StarVipUserCreateOpUpdateReq> result = starVipUserService.createOrUpdate(param);
                if(result.isSuccess()) {
                    return Result.success(registerSuccessVip(userId,data));
                }
            } else {
                //构建参数实体
                MemberRegisterReq param = buildRegisterParam(userDTO,req);
                Result<MemberRegisterReq> result = starVipUserService.register(param);
                if(result.isSuccess()) {
                    //注册成功修改本地会员为星赏会会员
                    return Result.success(registerSuccessVip(userId, result));
                }
            }
        }

        return Result.failed(new BusinessException(SystemError.SYS_433,"激活失败"));
    }

    private AppUserDTO registerSuccessVip(Long userId, Result<MemberRegisterReq> result) {
        MemberRegisterReq data = result.getData();
        User user = new User();
        user.setId(userId);
        user.setModifiedId(userId);
        user.setUserAccount(data.getCtsLogin());
        user.setMemberId(data.getId());
        user.setMemberCode(data.getCardNumber());
        user.setMemberDate(LocalDate.now());
        user.setMemberLevel(MemberLevel.SILVER.getLevel());
        user.setConsumptionTimes(MemberLevel.GOLD.getRatingConsumptionTimes());
        user.setUpgradePoints(MemberLevel.GOLD.getRatingPoint());
        user.setMemberPoints(MemberLevel.SILVER.getKeepPoint());
        String ctsLastName = data.getCtsLastName();
        String ctsFstName = data.getCtsFstName();
        user.setEnName(ctsLastName);
        user.setEnSurname(ctsFstName);
        user.setEnFullname(ctsFstName + ctsLastName);
        user.setGmtModified(LocalDateTime.now());
        user.setIsInternalMember(IsInternalMemberCode.YES.getCode());
        userMapper.update(user,Wrappers.<User>lambdaUpdate().eq(User::getId, userId));
        return updateCache(userId);
    }

    private AppUserDTO registerSuccessVip(Long userId, Contact contact) {
        LoyMember loyMember = contact.getListOfLoyMember().getLoyMember().get(0);
        LoyMemberDominantTier loyMemberDominantTier = loyMember.getListOfLOYMemberDominantTier().getLoyMemberDominantTier().get(0);
        User user = new User();
        user.setId(userId);
        user.setModifiedId(userId);
        user.setUserAccount(loyMember.getCtsLogin());
        user.setMemberId(loyMember.getId());
        user.setMemberCode(loyMember.getMemberNumber());
        user.setMemberDate(LocalDate.parse(loyMember.getStartDate(), DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss")));
        MemberLevel memberLevel = MemberLevel.getByLevel(Integer.valueOf(loyMemberDominantTier.getTierCode()));
        if(memberLevel != null) {
            // 生命周期定级积分
            Integer lifetimePoint1Value = Integer.valueOf(loyMember.getLifetimePoint1Value());
            // 定级消费次数
            Integer point2Value = Integer.valueOf(loyMember.getPoint2Value());
            // 可用积分
            Integer point1Value = Integer.valueOf(loyMember.getPoint1Value());
            user.setMemberLevel(memberLevel.getLevel());
            if(memberLevel.getLevel() == MemberLevel.FIVESTARS.getLevel()) {
                user.setUpgradePoints(0);
                user.setConsumptionTimes(0);
            } else {
                user.setUpgradePoints(MemberLevel.getNextLevel(memberLevel.getLevel()).getRatingPoint() - lifetimePoint1Value);
                user.setConsumptionTimes(MemberLevel.getNextLevel(memberLevel.getLevel()).getRatingConsumptionTimes() - point2Value);
            }

            user.setMemberPoints(point1Value);
        }

        String ctsLastName = contact.getLastName();
        String ctsFstName = contact.getFirstName();
        user.setEnName(ctsLastName);
        user.setEnSurname(ctsFstName);
        user.setEnFullname(ctsFstName + ctsLastName);
        user.setGmtModified(LocalDateTime.now());
        user.setIsInternalMember(IsInternalMemberCode.YES.getCode());
        userMapper.update(user,Wrappers.<User>lambdaUpdate().eq(User::getId, userId));
        return updateCache(userId);
    }

    private MemberRegisterReq buildRegisterParam(AppUserDTO userDTO, RegisterStartVipReq req) {
        MemberRegisterReq param = new MemberRegisterReq();
        param.setCellularPhone(userDTO.getContactMobileAreaCode() + "-" + userDTO.getContactNumber());
        param.setCtsLastName(req.getEnName());
        param.setCtsFstName(req.getEnSurname());
        Integer appellation = userDTO.getAppellation();
        if(appellation!= null) {
            //称谓(1:先生;2:女士;3:小姐;4:男童;5:女童)
            if(appellation == 1) {
                param.setMf("11");
            } else if(appellation == 2) {
                param.setMf("12");
            } else if(appellation == 3) {
                param.setMf("13");
            } else {
                //未知性别
                param.setMf("99");
            }
        } else {
            //未知性别
            param.setMf("99");
        }

        String ctsLogin = String.valueOf(System.currentTimeMillis());
        /*String ctsLogin = "CTS_" + req.getEnSurname() + req.getEnName();
        try {
            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(RedisConstants.USER_NAME_LOCK + ctsLogin, 1, Duration.ofSeconds(10));
            while (!aBoolean) {
                aBoolean = redisTemplate.opsForValue().setIfAbsent(RedisConstants.USER_NAME_LOCK + ctsLogin, 1, Duration.ofSeconds(10));
            }
            Integer num = (Integer) redisTemplate.opsForValue().get(RedisConstants.USER_NAME_NUM + ctsLogin);
            String key = ctsLogin;
            if(num == null) {
                num = 0;
            } else {
                key = ctsLogin + num;
            }
            redisTemplate.opsForValue().set(RedisConstants.USER_NAME_NUM + key, num + 1);
        } catch (Exception e) {
            log.error("获取redis锁失败",e);
        } finally {
            redisTemplate.delete(ctsLogin);
        }*/

        param.setCtsLogin(ctsLogin);
        param.setEnrollmentChannel("11");
        param.setCtsCardSegment("11");
        param.setCtsPassword(MD5Util.stringMd5(RandomUtil.randomString(6)));
        param.setActiveFlag("Y");
        param.setCardStatusCode("Active");
        param.setCardType("A00000");
        param.setCtsCompanyNumber("2199");

        return param;
    }

    @Override
    public Result setEmail(SetEmailReq req) {
        Long userId = req.getUserId();
        AppUserDTO userDTO = queryCacheUser(userId);
        String emailKey = RedisConstants.APP_EMAIL_CODE_KEY + userDTO.getId() + "_SET";
        Object trueCode = redisTemplate.opsForValue().get(emailKey);
        if (trueCode == null) {
            return Result.failed(SystemError.USER_1016);
        }
        if (!StringUtils.equals(req.getCode(), trueCode.toString())) {
            return Result.failed(SystemError.USER_1017);
        }
        User user = EntityUtil.copy(req, User.class);
        user.setModifiedId(userId);
        user.setGmtModified(LocalDateTime.now());
        userMapper.update(user,new UpdateWrapper<User>().lambda()
                .eq(User::getId, userId)
                .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
        updateCache(userId);
        redisTemplate.delete(emailKey);

        return Result.success();
    }

    @Override
    public Result setMailSend(String email, Long userId) {
        // 校驗郵箱
        if(!email.matches(emailReg)) {
            return Result.failed(new BusinessException(SystemError.SYS_433,"请输入规范邮箱格式"));
        }

        AppUserDTO userDTO  = queryCacheUser(userId);

        if(userDTO == null) {
            return Result.failed(new BusinessException(SystemError.USER_1002));
        }

        String key = RedisConstants.APP_EMAIL_CODE_KEY + userDTO.getId() + "_SET";
        Object o = redisTemplate.opsForValue().get(key);

        if(o != null) {
            // 邮件间隔两分钟可重发
            Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
            if(expire > 15 * 60 - 120) {
                throw new BusinessException(SystemError.USER_1024);
            }
        }

        String userDTOEmail = userDTO.getEmail();
        if(userDTOEmail != null) {
            return Result.failed(new BusinessException(SystemError.SYS_433,"该用户已设置邮箱"));
        }

        List<User> userList= userMapper.selectList(Wrappers.<User>lambdaQuery().eq(User::getEmail,email));
        if(CollectionUtils.isNotEmpty(userList)) {
            return Result.failed(new BusinessException(SystemError.SYS_433,"該郵箱已被使用"));
        }



        String title = "设置邮箱";
        String content = "您正在设置邮箱，验证码：";
        String code = RandomUtil.randomNumbers(4);
        content = content + code;

        redisTemplate.opsForValue().set(key, code,15, TimeUnit.MINUTES);
        mailTemplate.sendSimpleMail(email, title, content);

        return Result.success();
    }

    @Override
    public Result<NumDTO> getNum(Long userId) {
        AppUserDTO userDTO = queryCacheUser(userId);
        Integer collectionNum = userCollectionService.countByUserId(userId);
        Integer footPointNum = userFootPointService.countByUserId(userId);
        Integer messageNum = userMessageService.countByUserId(userId);
        NumDTO numDTO = new NumDTO();
        /*UserInfoQueryReq req = new UserInfoQueryReq();
        req.setCtsLogin(userDTO.getUserAccount());

        try {
            Result<Contact> contactResult = starVipUserService.userInfoQuery(req);
            if(contactResult.isSuccess()) {
                LoyMember member = contactResult.getData().getListOfLoyMember().getLoyMember().get(0);
                numDTO.setIntegral(Integer.valueOf(member.getPoint1Value()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }*/

        numDTO.setCollectionNum(collectionNum);
        numDTO.setFootPointNum(footPointNum);
        numDTO.setMessageNum(messageNum);
        numDTO.setIntegral(userDTO.getMemberPoints());
        return Result.success(numDTO);
    }

    @Override
    public Result update(@Valid UpdateUserReq req) {
        User user = EntityUtil.copy(req, User.class);
        Long userId = req.getId();
        user.setEnFullname(req.getEnSurname() + req.getEnName());
        user.setModifiedId(userId);
        user.setGmtModified(LocalDateTime.now());
        userMapper.update(user,new UpdateWrapper<User>().lambda()
                .eq(User::getId,userId)
                .eq(User::getIsDeleted, IsDeletedCode.NO.getCode()));
        updateCache(userId);

        return Result.success();
    }

    public static void main(String[] args) {
        DateTimeFormatter timeDtf = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss");
        String s = "09/28/2010 00:00:00";
        LocalDate parse = LocalDate.parse(s,timeDtf);
        System.out.println(parse);
    }

    /**
     * 校验密码是否是最近使用过的密码
     */
    private void checkRecentPwd(Long userId,String plaintext,String nowPassWord) throws Exception{
        // 校验密码是否和最近三次相同
        redisTemplate.opsForList().trim(RedisConstants.RECENT_PASSWORD_USER_ID + userId,0,2);
        List<Object> range = redisTemplate.opsForList().range(RedisConstants.RECENT_PASSWORD_USER_ID + userId, 0, 2);
        if(CollectionUtils.isEmpty(range)) {
            boolean checkpw = BCrypt.checkpw(plaintext, nowPassWord);
            if(checkpw) {
                throw new BusinessException(SystemError.USER_1026);
            }
        }
        for (Object o : range) {
            String recentPwd = (String) o;
            boolean checkpw = BCrypt.checkpw(plaintext, recentPwd);
            if(checkpw) {
                throw new BusinessException(SystemError.USER_1026);
            }
        }
    }

}
